package jcu.sal.common.agents.netty;

import static org.jboss.netty.channel.Channels.write;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

import jcu.sal.common.StreamID;
import jcu.sal.common.CommandFactory.Command;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelPipelineCoverage;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;

/**
 * @author Marc Hammerton
 * 
 */
@ChannelPipelineCoverage("all")
public class NettyClientMessageEncoder extends SimpleChannelHandler {

	@Override
	public void writeRequested(ChannelHandlerContext chx, MessageEvent e)
			throws Exception {
		
		ChannelBuffer buf = ChannelBuffers.dynamicBuffer();

		// Messages without data
		if(e.getMessage() instanceof Short) {							
			buf.writeShort((Short) e.getMessage());
		}
		// Messages with data
		else {
			NettyMessage message = (NettyMessage) e.getMessage();
			byte[] data = null;
			buf.writeShort(message.getType());
			switch (message.getType()) {
				case NettyConstants.REQ_REGISTER:
					data = ((String) message.getData(0)).getBytes();
					buf.writeInt(data.length);
					buf.writeBytes(data);
					break;
				case NettyConstants.REQ_ADD_PROTOCOL:
					boolean loadSensors = (Boolean) message.getData(0);
					data = ((String) message.getData(1)).getBytes();
					buf.writeInt(data.length + 1);					
					buf.writeByte((loadSensors) ? (byte) 1 : (byte) 0);
					buf.writeBytes(data);
					break;
				case NettyConstants.REQ_ADD_SENSOR:
					data = ((String) message.getData(0)).getBytes();
					buf.writeInt(data.length);
					buf.writeBytes(data);
					break;
				case NettyConstants.REQ_GET_CML:
					data = ((String) message.getData(0)).getBytes();
					buf.writeInt(data.length);
					buf.writeBytes(data);
					break;
				case NettyConstants.REQ_LIST_SENSOR:
					data = ((String) message.getData(0)).getBytes();
					buf.writeInt(data.length);
					buf.writeBytes(data);
					break;
				case NettyConstants.REQ_REMOVE_PROTOCOL:
					boolean removeSensors = (Boolean) message.getData(0);
					data = ((String) message.getData(1)).getBytes();
					buf.writeInt(data.length + 1);					
					buf.writeByte((removeSensors) ? (byte) 1 : (byte) 0);
					buf.writeBytes(data);
					break;
				case NettyConstants.REQ_REMOVE_SENSOR:
					data = ((String) message.getData(0)).getBytes();
					buf.writeInt(data.length);
					buf.writeBytes(data);
					break;
				case NettyConstants.REQ_SETUP_STREAM:
					Command c = (Command) message.getData(0);
					byte[] commandData = convertObjectToByteArray(c);
					data = ((String) message.getData(1)).getBytes();
					buf.writeInt(commandData.length + data.length);
					buf.writeInt(commandData.length);
					buf.writeBytes(commandData);
					buf.writeBytes(data);
					break;
				case NettyConstants.REQ_START_STREAM:
					data = convertObjectToByteArray((StreamID) message.getData(0));
					buf.writeInt(data.length);
					buf.writeBytes(data);
					break;
				case NettyConstants.REQ_TERMINATE_STREAM:
					data = convertObjectToByteArray((StreamID) message.getData(0));
					buf.writeInt(data.length);
					buf.writeBytes(data);
					break;
				case NettyConstants.REQ_REG_EVENTHANDLER:
					data = ((String) message.getData(0)).getBytes();
					buf.writeInt(data.length);
					buf.writeBytes(data);
					break;
				case NettyConstants.REQ_UNREG_EVENTHANDLER:
					data = ((String) message.getData(0)).getBytes();
					buf.writeInt(data.length);
					buf.writeBytes(data);
					break;
				default:
					break;
			}
		}		

		// Send the constructed message.
		write(chx, e.getChannel(), e.getFuture(), buf, e.getRemoteAddress());
	}
	
	private byte[] convertObjectToByteArray(Object object) {
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		ObjectOutputStream oos;
		try {
			oos = new ObjectOutputStream(bos);
			oos.writeObject(object);
			oos.flush();
			oos.close();
			bos.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		byte[] data = bos.toByteArray();
		
		return data;
	}
}
